﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Reflection;
using SewerAPI.Util;

namespace SewerAPI.Core
{
    /// <summary>
    /// Abstract message class
    /// </summary>
    public abstract class Message
    {
        #region Private Fields

        string text = string.Empty;
        string channel = Constants.DEFAULTCHANNEL;
        string from = Configuration.Alias;
        int maxtextlength = Constants.MAXTEXTLENGTH;
        MessageTransaction transaction = MessageTransaction.Unknown;
        const string SPLITTER = "||";
        DateTime timestamp;

        #endregion

        #region Public Constant Fields

        public const char NAMEVALUETOKEN = '&';
        public const char VALUETOKEN = '=';

        #endregion

        #region Public Fields

        /// <summary>
        /// Maximum text length
        /// </summary>
        public virtual int MaxTextLength
        {
            get
            {
                return maxtextlength;
            }
        }

        /// <summary>
        /// The name where it's send from
        /// </summary>
        [MessageExport]
        public string From
        {
            get
            {
                return from;
            }
            set
            {
                from = value;
            }
        }

        /// <summary>
        /// The channel name
        /// </summary>
        [MessageExport]
        public string Channel
        {
            get
            {
                return channel;
            }
            set {
                channel = value;
            }
        }

        /// <summary>
        /// The endpoint to
        /// </summary>
        public IPEndPoint To
        {
            get
            {
                return GetTo();
            }
        }

        /// <summary>
        /// Get the message type
        /// </summary>
        [MessageExport(IsTypeHint=true)]
        public MessageType MessageType
        {
            get
            {
                return GetMessageType();
            }
        }

        /// <summary>
        /// Timestamp of the message
        /// </summary>
        public DateTime TimeStamp
        {
            get
            {
                return timestamp;
            }
        }

        /// <summary>
        /// Get the message transaction
        /// </summary>
        [MessageExport]
        public MessageTransaction Transaction
        {
            get 
            {
                return transaction;
            }
            set 
            {
                transaction = value;
            }
        }
       
        /// <summary>
        /// The text of a message
        /// </summary>
        [MessageExport]
        public string Text
        {
            get
            {
                return text;
            }
            set 
            {
                if (isValidLength(value))
                {
                    text = value;
                }
                else
                {
                    throw new Exception(
                        string.Format(Resources.Strings.ERR01, MaxTextLength));
                }
            }
        }

        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="text"></param>
        public Message(string text) : this()
        {
            //Text will be validated
            this.Text = text;

        }

        /// <summary>
        /// Private empty constructor
        /// </summary>
        private Message() 
        {
            //Set the timestamp
            timestamp = DateTime.Now;
        }

        #region Util Methods

        public Statistic CreateStatistic() 
        {
            if (this.Transaction != MessageTransaction.Unknown)
            {
                return Statistic.Create(this);
            }
            else return null;
        }

        bool isValidLength(string s)
        {
            if (!string.IsNullOrEmpty(s))
            {
                return s.Length <= MaxTextLength;
            }
            else return false;
        }

        #endregion

        #region Abstract Methods

        /// <summary>
        /// Get the message type
        /// </summary>
        /// <returns></returns>
        public abstract MessageType GetMessageType();

        /// <summary>
        /// Get the IP EndPoint
        /// </summary>
        /// <returns></returns>
        public abstract IPEndPoint GetTo();

        #endregion

        #region Encryption and Serialization Methods

        /// <summary>
        /// Serialize a message to a string
        /// </summary>
        /// <returns></returns>
        public string Serialize()
        {
            return TokenizeDictionary(GetExportDictionary());
        }

        /// <summary>
        /// Get all exportable properties
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        internal IEnumerable<PropertyInfo> GetExportableProperties() 
        {
            Type _t = this.GetType();
            PropertyInfo[] _pis = _t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo _pi in _pis)
            {
                MessageExportAttribute[] _attrs = (MessageExportAttribute[])_pi.GetCustomAttributes(typeof(MessageExportAttribute), true);
                if (_attrs.Length == 1) 
                {
                    yield return _pi;
                }
            }
        }

        /// <summary>
        /// Get all exportable properties
        /// </summary>
        /// <returns></returns>
        internal IEnumerable<MessageExportAttribute> GetExportableAttributes()
        {
            Type _t = this.GetType();
            PropertyInfo[] _pis = _t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo _pi in _pis)
            {
                MessageExportAttribute[] _attrs = (MessageExportAttribute[])_pi.GetCustomAttributes(typeof(MessageExportAttribute), true);
                if (_attrs.Length == 1)
                {
                    yield return _attrs[0];
                }
            }
        }

        /// <summary>
        /// Get the export attribute for a message
        /// </summary>
        /// <param name="pi"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        protected MessageExportAttribute GetExportAttribute(PropertyInfo pi)
        {
            try
            {
                MessageExportAttribute[] _attrs = 
                    (MessageExportAttribute[])pi.GetCustomAttributes(typeof(MessageExportAttribute), true);
                return _attrs[0];
            }
            catch
            {
                string _exmsg = string.Format("Message property {0} has no export attribute", pi.Name);
                SewerLogger.Instance.Error(_exmsg);
                throw new NullReferenceException(_exmsg);
            }
        }

        /// <summary>
        /// Get the export key of a property. If none is found
        /// get the propertyname to lowercase
        /// </summary>
        /// <param name="pi"></param>
        /// <returns></returns>
        protected string GetExportKey(PropertyInfo pi)
        {
            MessageExportAttribute _attr = GetExportAttribute(pi);
            return (_attr.HasValue) ? _attr.Name : pi.Name.ToLowerInvariant();
        }

        /// <summary>
        /// Get the property key and the property value as a keyvaluepair.
        /// In this version only strings are allowed
        /// </summary>
        /// <param name="pi"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        protected KeyValuePair<string, string> GetExportKeyValuePair(PropertyInfo pi)
        {
            string _key = GetExportKey(pi);
            string _value = pi.GetValue(this, new object[] { }).ToString();
            return new KeyValuePair<string, string>(_key, _value);
        }

        /// <summary>
        /// Get the export dictionary of a message
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected Dictionary<string, string> GetExportDictionary()
        {
            Dictionary<string, string> _dict = new Dictionary<string, string>();
            foreach (PropertyInfo _pi in GetExportableProperties())
            {
                KeyValuePair<string, string> _kv = GetExportKeyValuePair(_pi);
                _dict.Add(_kv.Key, _kv.Value);
            }
            return _dict;
        }

        /// <summary>
        /// Tokenize a dictionary to a string
        /// </summary>
        /// <param name="dict"></param>
        /// <returns></returns>
        protected string TokenizeDictionary(Dictionary<string, string> dict)
        {
            var _dictarr = dict.Select(x => x.Key + VALUETOKEN +
                TokenEncode(x.Value)).ToArray();
            return string.Join(NAMEVALUETOKEN.ToString(), _dictarr);
        }

        /// <summary>
        /// Split a string based on the namevalue token and the value token
        /// into a dictionary
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected Dictionary<string, string> UntokenizeString(string value)
        {
            var _arr = value.Split(new char[] { NAMEVALUETOKEN }, StringSplitOptions.None);
            var _dict = new Dictionary<string, string>();
            foreach (string _arrel in _arr)
            {
                try
                {
                    var _arrelarr = _arrel.Split(new char[] { VALUETOKEN }, StringSplitOptions.None);
                    _dict.Add(_arrelarr[0], TokenDecode(_arrelarr[1]));
                }
                catch
                {
                    SewerLogger.Instance.Debug("Cannot split value '{0}'", _arrel);
                }
            }
            return _dict;
        }

        /// <summary>
        /// Make sure that the valuetoken and the namevalue token
        /// doesn't exist in the value strings
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string TokenEncode(string value)
        {
            var _value = value.Replace(VALUETOKEN.ToString(), safeTokenAlternative(VALUETOKEN));
            return _value.Replace(NAMEVALUETOKEN.ToString(), safeTokenAlternative(NAMEVALUETOKEN));
        }

        /// <summary>
        /// Remove possible token values after tokenization
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        protected string TokenDecode(string value)
        {
            var _value = value.Replace(safeTokenAlternative(NAMEVALUETOKEN), NAMEVALUETOKEN.ToString());
            return _value.Replace(safeTokenAlternative(VALUETOKEN), VALUETOKEN.ToString());
        }

        private string safeTokenAlternative(char token)
        {
            return string.Format("{0}{0}", token);
        }

        /// <summary>
        /// Encrypt a message
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public static byte[] Encrypt(Message m)
        {
            return Configuration.EncryptionEngine.EncryptMessage(m);
        }

        public static Message Decrypt(byte[] encryptedmessage) 
        {
            Configuration.EncryptionEngine.DecryptMessage(encryptedmessage);
        }


        #endregion
    }
}
